{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# bqplot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "This notebook is meant to guide you through the first stages of using the `bqplot` visualization library. `bqplot` is a Grammar of Graphics based interactive visualization library for the Jupyter notebook where every single component of a plot is an interactive iPython widget. What this means is that even after a plot is drawn, you can change almost any aspect of it. This makes the creation of advanced Graphical User Interfaces attainable through just a few simple lines of Python code."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's begin by importing some libraries we'll need\n",
    "import numpy as np"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# And creating some random data\n",
    "size = 100\n",
    "np.random.seed(0)\n",
    "x_data = np.arange(size)\n",
    "y_data = np.cumsum(np.random.randn(size)  * 100.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Your First Plot"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's start by creating a simple Line chart. `bqplot` has two different APIs, the first one is a `matplotlib` inspired simple API called `pyplot`. So let's import that."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import pyplot as plt"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Let's plot `y_data` against `x_data`, and then `show` the plot."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "plt.figure(title='My First Plot')\n",
    "plt.plot(x_data, y_data)\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Use the buttons above to Pan (or Zoom), Reset or save the Figure."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "## Using `bqplot`'s interactive elements"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, let's try creating a new plot. First, we create a brand new `Figure`. The `Figure` is the final element of any plot that is eventually displayed. You can think of it as a Canvas on which we put all of our other plots."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Creating a new Figure and setting it's title\n",
    "plt.figure(title='My Second Chart')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's assign the scatter plot to a variable\n",
    "scatter_plot = plt.scatter(x_data, y_data)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {
    "scrolled": false
   },
   "outputs": [],
   "source": [
    "# Let's show the plot\n",
    "plt.show()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Since both the x and the y attributes of a bqplot chart are interactive widgets, we can change them. So, let's \n",
    "change the y attribute of the chart."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scatter_plot.y = np.cumsum(np.random.randn(size)  * 100.0)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Re-run the above cell a few times, the same plot should update everytime. But, thats not the only thing that can be changed once a plot has been rendered. Let's try changing some of the other attributes."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Say, the color\n",
    "scatter_plot.colors = ['Red']"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Or, the marker style\n",
    "scatter_plot.marker = 'diamond'"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "It's important to remember that an interactive widget means that the `JavaScript` and the `Python` communicate. So, the plot can be changed through a single line of python code, or a piece of python code can be triggered by a change in the plot. Let's go through a simple example. Say we have a function `foo`:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "def foo(change):\n",
    "    print('This is a trait change. Foo was called by the fact that we moved the Scatter')\n",
    "    print('In fact, the Scatter plot sent us all the new data: ')\n",
    "    print('To access the data, try modifying the function and printing the data variable')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "We can call `foo` everytime any attribute of our scatter is changed. Say, the `y` values:"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# First, we hook up our function `foo` to the colors attribute (or Trait) of the scatter plot\n",
    "scatter_plot.observe(foo, 'y')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To allow the points in the `Scatter` to be moved interactively, we set the `enable_move` attribute to `True`"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scatter_plot.enable_move = True"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Go ahead, head over to the chart and move any point in some way. This move (which happens on the `JavaScript` side should trigger our `Python` function `foo`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "## Understanding how bqplot uses the Grammar of Graphics paradigm"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "`bqplot` has two different APIs. One is the matplotlib inspired `pyplot` which we used above (you can think of it as similar to `qplot` in `ggplot2`). The other one, the verbose API, is meant to expose every element of a plot individually, so that their attriutes can be controlled in an atomic way. In order to truly use `bqplot` to build complex and feature-rich GUIs, it pays to understand the underlying theory that is used to create a plot."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "To understand this verbose API, it helps to revisit what exactly the components of a plot are. The first thing we need is a `Scale`."
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "**A `Scale` is a mapping from (function that converts) data coordinates to figure coordinates.** What this means is that, a `Scale` takes a set of values in any arbitrary unit (say number of people, or $, or litres) and converts it to pixels (or colors for a `ColorScale`)."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# First, we import the scales\n",
    "from bqplot import LinearScale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# Let's create a scale for the x attribute, and a scale for the y attribute\n",
    "x_sc = LinearScale()\n",
    "y_sc = LinearScale()"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we need to create the actual `Mark` that will visually represent the data. Let's pick a `Scatter` chart to start."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import Scatter"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scatter_chart = Scatter(x=x_data, y=y_data, scales={'x': x_sc, 'y': y_sc})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Most of the time, the actual `Figure` co-ordinates don't really mean anything to us. So, what we need is the visual representation of our `Scale`, which is called an `Axis`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import Axis"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "x_ax = Axis(label='X', scale=x_sc)\n",
    "y_ax = Axis(label='Y', scale=y_sc, orientation='vertical')"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "And finally, we put it all together on a canvas, which is called a `Figure`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import Figure"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig = Figure(marks=[scatter_chart], title='A Figure', axes=[x_ax, y_ax])\n",
    "fig"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "The IPython display machinery displays the last returned value of a cell. If you wish to explicitly display a widget, you can call `IPython.display.display`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from IPython.display import display"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "display(fig)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, that the plot has been generated, we can control every single attribute of it. Let's say we wanted to color the chart based on some other data."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# First, we generate some random color data.\n",
    "color_data = np.random.randint(0, 2, size=100)"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Now, we define a ColorScale to map the color_data to actual colors"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import ColorScale"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# The colors trait controls the actual colors we want to map to. It can also take a min, mid, max list of\n",
    "# colors to be interpolated between for continuous data.\n",
    "col_sc = ColorScale(colors=['MediumSeaGreen', 'Red'])"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "scatter_chart.scales = {'x': x_sc, 'y': y_sc, 'color': col_sc}\n",
    "# We pass the color data to the Scatter Chart through it's color attribute\n",
    "scatter_chart.color = color_data"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {
    "collapsed": true
   },
   "source": [
    "The grammar of graphics framework allows us to overlay multiple visualizations on a single `Figure` by having the visualization share the `Scales`. So, for example, if we had a `Bar` chart that we would like to plot alongside the `Scatter` plot, we just pass it the same `Scales`."
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "from bqplot import Bars"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "new_size = 50\n",
    "scale = 100.\n",
    "x_data_new = np.arange(new_size)\n",
    "y_data_new = np.cumsum(np.random.randn(new_size)  * scale)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "# All we need to do to add a bar chart to the Figure is pass the same scales to the Mark\n",
    "bar_chart = Bars(x=x_data_new, y=y_data_new, scales={'x': x_sc, 'y': y_sc})"
   ]
  },
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "Finally, we add the new `Mark` to the `Figure` to update the plot!"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "metadata": {},
   "outputs": [],
   "source": [
    "fig.marks = [scatter_chart, bar_chart]"
   ]
  }
 ],
 "metadata": {
  "anaconda-cloud": {},
  "kernelspec": {
   "display_name": "Python 3",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.7.6"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 1
}
